#include <dspSimulator/instruction/and.h>

bool AND::checkCondition(std::shared_ptr<DspMachine> dspMachine) {
    if (isCompactInstruction) {
        return true;
    } else {
        return checkStandardCondition(dspMachine);
    }
}

void AND::loadInstIRIntoJit(llvm::LLVMContext *llvmContext, llvm::Module *module,
                            std::shared_ptr<llvm::IRBuilder<>> irBuilder) {
    llvm::FunctionType *funcType = llvm::FunctionType::get(irBuilder->getVoidTy(), false);

    // --------------------------------- AND (.unit) op1,xop2,dst ---------------------------------------------
    {
        llvm::Function *andFunc = llvm::Function::Create(funcType, llvm::GlobalValue::ExternalLinkage, "ANDKind1",
                                                         module);
        llvm::BasicBlock *entryBB = llvm::BasicBlock::Create(*llvmContext, "entry", andFunc);
        irBuilder->SetInsertPoint(entryBB);

        auto src2RegPtr = getSrc2LoRegPtr(module, irBuilder);
        auto src2Reg = irBuilder->CreateLoad(src2RegPtr);

        auto src1RegPtr = getSrc1LoRegPtr(module, irBuilder);
        auto src1Reg = irBuilder->CreateLoad(src1RegPtr);

        auto andVal = irBuilder->CreateAnd(src1Reg, src2Reg);

        auto dstRegPtr = getDstLoRegPtr(module, irBuilder);

        irBuilder->CreateStore(andVal, dstRegPtr);
        irBuilder->CreateRetVoid();
    }

    // --------------------------------- AND (.unit) scst5,xop2,dst---------------------------------------------
    {
        llvm::Function *andFunc = llvm::Function::Create(funcType, llvm::GlobalValue::ExternalLinkage, "ANDKind2",
                                                         module);
        llvm::BasicBlock *entryBB = llvm::BasicBlock::Create(*llvmContext, "entry", andFunc);
        irBuilder->SetInsertPoint(entryBB);

        auto src2RegPtr = getSrc2LoRegPtr(module, irBuilder);
        auto src2Reg = irBuilder->CreateLoad(src2RegPtr);

        auto scst5Val = getCstN(module, irBuilder, 5);
        auto scst5SExt = irBuilder->CreateSExt(scst5Val, irBuilder->getInt32Ty());

        auto andVal = irBuilder->CreateAnd(scst5SExt, src2Reg);

        auto dstRegPtr = getDstLoRegPtr(module, irBuilder);

        irBuilder->CreateStore(andVal, dstRegPtr);
        irBuilder->CreateRetVoid();
    }

    // --------------------------------- AND (.unit) dwop1,xdwop2,dwdst---------------------------------------------
    {
        llvm::Function *andFunc = llvm::Function::Create(funcType, llvm::GlobalValue::ExternalLinkage, "ANDKind3",
                                                         module);
        llvm::BasicBlock *entryBB = llvm::BasicBlock::Create(*llvmContext, "entry", andFunc);
        irBuilder->SetInsertPoint(entryBB);

        auto dstLoRegPtr = getDstLoRegPtr(module, irBuilder);
        auto dstHiRegPtr = getDstHiRegPtr(module, irBuilder);

        auto src2LoRegPtr = getSrc2LoRegPtr(module, irBuilder);
        auto src2HiRegPtr = getSrc2HiRegPtr(module, irBuilder);

        auto src2Lo = irBuilder->CreateLoad(src2LoRegPtr);
        auto src2Hi = irBuilder->CreateLoad(src2HiRegPtr);

        auto src1LoRegPtr = getSrc1LoRegPtr(module, irBuilder);
        auto src1HiRegPtr = getSrc1HiRegPtr(module, irBuilder);

        auto src1Lo = irBuilder->CreateLoad(src1LoRegPtr);
        auto src1Hi = irBuilder->CreateLoad(src1HiRegPtr);

        auto dstLo = irBuilder->CreateAnd(src1Lo, src2Lo);
        auto dstHi = irBuilder->CreateAnd(src1Hi, src2Hi);

        irBuilder->CreateStore(dstLo, dstLoRegPtr);
        irBuilder->CreateStore(dstHi, dstHiRegPtr);
        irBuilder->CreateRetVoid();
    }
}

std::shared_ptr<Instruction> AND::decode(std::shared_ptr<InstInfo> instInfo, u64 baseCycle) {
    std::shared_ptr<AND> _and;
    auto dspInstInfo = std::dynamic_pointer_cast<DspInstInfo>(instInfo);
    bool isStandardInst = dspInstInfo->isStandardInstruction();
    if (isStandardInst) {
        auto data = vectorToBitSet<32>(dspInstInfo->getBits());
        if (checkBits<10>(data, 2, 0b1111011110) && existStandardCondition(data)) {
            // Opcode for .L Unit, 1/2 src  op1,xop2,dst
            _and = std::make_shared<AND>(dspInstInfo->getAddress(), baseCycle);
            _and->creg_z = extractBits<4>(data, 28);
            _and->dst = extractBits<5>(data, 23).to_ulong();
            _and->src2 = extractBits<5>(data, 18).to_ulong();
            _and->src1 = extractBits<5>(data, 13).to_ulong();
            _and->x = data[12];
            _and->s = data[1];
            _and->p = data[0];
            if (_and->s == 0) {
                _and->funcUnit = FuncUnit::L1;
            } else {
                _and->funcUnit = FuncUnit::L2;
            }
            _and->instFormat = InstFormat::Kind1;
        } else if (checkBits<10>(data, 2, 0b1111010110) && existStandardCondition(data)) {
            // Opcode for .L Unit, 1/2 src  scst5,xop2,dst
            _and = std::make_shared<AND>(dspInstInfo->getAddress(), baseCycle);
            _and->creg_z = extractBits<4>(data, 28);
            _and->dst = extractBits<5>(data, 23).to_ulong();
            _and->src2 = extractBits<5>(data, 18).to_ulong();
            _and->cst = extractBits<5>(data, 13).to_ulong();
            _and->x = data[12];
            _and->s = data[1];
            _and->p = data[0];
            if (_and->s == 0) {
                _and->funcUnit = FuncUnit::L1;
            } else {
                _and->funcUnit = FuncUnit::L2;
            }
            _and->instFormat = InstFormat::Kind2;
        } else if (checkBits<10>(data, 2, 0b1111011110) && checkBits<4>(data, 28, 0b0001)) {
            // Opcode for .L Unit, 1/2 src - same as L2S but fixed hdr, bit 23-msb of opcode
            _and = std::make_shared<AND>(dspInstInfo->getAddress(), baseCycle);
            _and->isCompactInstruction = false;
            _and->dst = extractBits<5>(data, 23).to_ulong();
            _and->src2 = extractBits<5>(data, 18).to_ulong();
            _and->src1 = extractBits<5>(data, 13).to_ulong();
            _and->x = data[12];
            _and->s = data[1];
            _and->p = data[0];
            if (_and->s == 0) {
                _and->funcUnit = FuncUnit::L1;
            } else {
                _and->funcUnit = FuncUnit::L2;
            }
            _and->instFormat = InstFormat::Kind3;
        } else if (checkBits<10>(data, 2, 0b0111111000) && existStandardCondition(data)) {
            // Opcode for .S Unit, 1/2 src
            _and = std::make_shared<AND>(dspInstInfo->getAddress(), baseCycle);
            _and->creg_z = extractBits<4>(data, 28);
            _and->dst = extractBits<5>(data, 23).to_ulong();
            _and->src2 = extractBits<5>(data, 18).to_ulong();
            _and->src1 = extractBits<5>(data, 13).to_ulong();
            _and->x = data[12];
            _and->s = data[1];
            _and->p = data[0];
            if (_and->s == 0) {
                _and->funcUnit = FuncUnit::S1;
            } else {
                _and->funcUnit = FuncUnit::S2;
            }
            _and->instFormat = InstFormat::Kind1;
        } else if (checkBits<10>(data, 2, 0b0111101000) && existStandardCondition(data)) {
            // Opcode for .S Unit, 1/2 src
            _and = std::make_shared<AND>(dspInstInfo->getAddress(), baseCycle);
            _and->creg_z = extractBits<4>(data, 28);
            _and->dst = extractBits<5>(data, 23).to_ulong();
            _and->src2 = extractBits<5>(data, 18).to_ulong();
            _and->cst = extractBits<5>(data, 13).to_ulong();
            _and->x = data[12];
            _and->s = data[1];
            _and->p = data[0];
            if (_and->s == 0) {
                _and->funcUnit = FuncUnit::S1;
            } else {
                _and->funcUnit = FuncUnit::S2;
            }
            _and->instFormat = InstFormat::Kind2;
        } else if (checkBits<10>(data, 2, 0b0111111000) && checkBits<4>(data, 28, 0b0001)) {
            _and = std::make_shared<AND>(dspInstInfo->getAddress(), baseCycle);
            _and->dst = extractBits<5>(data, 23).to_ulong();
            _and->src2 = extractBits<5>(data, 18).to_ulong();
            _and->src1 = extractBits<5>(data, 13).to_ulong();
            _and->x = data[12];
            _and->s = data[1];
            _and->p = data[0];
            if (_and->s == 0) {
                _and->funcUnit = FuncUnit::S1;
            } else {
                _and->funcUnit = FuncUnit::S2;
            }
            _and->instFormat = InstFormat::Kind3;
        } else if (checkBits<10>(data, 2, 0b1001101100) && existStandardCondition(data)) {
            // Opcode for .D Unit, 2 src
            _and = std::make_shared<AND>(dspInstInfo->getAddress(), baseCycle);
            _and->creg_z = extractBits<4>(data, 28);
            _and->dst = extractBits<5>(data, 23).to_ulong();
            _and->src2 = extractBits<5>(data, 18).to_ulong();
            _and->src1 = extractBits<5>(data, 13).to_ulong();
            _and->x = data[12];
            _and->s = data[1];
            _and->p = data[0];
            if (_and->s == 0) {
                _and->funcUnit = FuncUnit::D1;
            } else {
                _and->funcUnit = FuncUnit::D2;
            }
            _and->instFormat = InstFormat::Kind1;
        } else if (checkBits<10>(data, 2, 0b1001111100) && existStandardCondition(data)) {
            // Opcode for .D Unit, 2 src   scst5,xop2,dst
            _and = std::make_shared<AND>(dspInstInfo->getAddress(), baseCycle);
            _and->creg_z = extractBits<4>(data, 28);
            _and->dst = extractBits<5>(data, 23).to_ulong();
            _and->src2 = extractBits<5>(data, 18).to_ulong();
            _and->cst = extractBits<5>(data, 13).to_ulong();
            _and->x = data[12];
            _and->s = data[1];
            _and->p = data[0];
            if (_and->s == 0) {
                _and->funcUnit = FuncUnit::D1;
            } else {
                _and->funcUnit = FuncUnit::D2;
            }
            _and->instFormat = InstFormat::Kind2;
        }
    } else {
        auto data = vectorToBitSet<16>(dspInstInfo->getBits());
        if (data[11] == 0 && checkBits<2>(data, 5, 0b00) && checkBits<3>(data, 1, 0b100)) {
            _and = std::make_shared<AND>(dspInstInfo->getAddress(), baseCycle);
            _and->isCompactInstruction = true;
            _and->compactInstKind = "L2c";
            _and->src1 = extractBits<3>(data, 13).to_ulong();
            _and->src2 = extractBits<3>(data, 7).to_ulong();
            _and->dst = data[4];
            _and->x = data[12];
            _and->s = data[0];
            if (_and->s == 0) {
                _and->funcUnit = FuncUnit::L1;
            } else {
                _and->funcUnit = FuncUnit::L2;
            }
            _and->DSZ = dspInstInfo->getDSZ();
            _and->RS = dspInstInfo->getRS();
            _and->SAT = dspInstInfo->getSAT();
            _and->PROT = dspInstInfo->getPROT();
            _and->BR = dspInstInfo->getBR();
            _and->p = dspInstInfo->getP();
            _and->fixUpRegOnCompactInstruction();
            _and->instFormat = InstFormat::Kind1;
        }
    }
    return _and;
}

void AND::executeCustom(std::shared_ptr<TargetMachine> targetMachine, ExecutePhase executePhase) {

}

std::shared_ptr<DspInstruction> AND::clone() {
    return std::make_shared<AND>(*this);
}

std::string AND::toString() const {
    switch (instFormat) {
        case InstFormat::Kind1:
            return getStandardConditionString() + " " + m_name + " " + FuncUnitName[static_cast<u32>(funcUnit)] + " " +
                   getSrc1Reg32Name() + "," + getSrc2Reg32Name() + "," + getDstReg32Name();
        case InstFormat::Kind2:
            return getStandardConditionString() + " " + m_name + " " + FuncUnitName[static_cast<u32>(funcUnit)] + " " +
                   dec2hex(extractBitsAndToInt<5>(cst)) + "," + getSrc2Reg32Name() + "," + getDstReg32Name();
        case InstFormat::Kind3:
            return m_name + " " + FuncUnitName[static_cast<u32>(funcUnit)] + " " + getSrc1Reg64Name() + "," +
                   getSrc2Reg64Name() + "," + getDstReg64Name();
        default:
            return {};
    }
}

